import ij.IJ;
import ij.ImagePlus;
import ij.gui.PolygonRoi;
import ij.plugin.filter.PlugInFilter;
import ij.process.Blitter;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;

import java.awt.Color;
import java.awt.Point;
import java.awt.Polygon;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Vector;

public class Tableau_detect_board_borders implements PlugInFilter {

	public static final byte PIXEL_BACKGROUND = (byte) 0xFF;
	public static final byte PIXEL_INFO = 0;
	
	public static final Color VERTICAL_CP_COLOR = Color.GREEN;
	public static final Color HORIZONTAL_CP_COLOR = Color.GREEN;
	public static final Color CP_MEAN_COLOR = Color.WHITE;
	public static final Color HORIZONTAL_CP_BOX_COLOR = Color.RED;
	public static final Color VERTICAL_CP_BOX_COLOR = Color.RED;
	
	
	
	//adjustable attributes, loading default values
	private static double _relativeDiference = 0.0091;
	private static int _minDiference = 10;
	private static double _internalRatio = 0.00165;
	private static double _externalRatio = 0.0091;
	private static int _blockTotal = 9;
	private static int _searchPoints = 3;
	private static double _borderInformationRatio = 0.65;
	private static int CP_NEIGHBOORS_COMPARISION = 2;
	private static double CP_MINIMUM_SLOPE = 0.07;
	
	//state attributes
	private short[][] _informationColumns;
	private short[][] _informationRows;

	private ColorProcessor _cpDebugStep12;
	private ColorProcessor _cpDebugStep3;
	private ImagePlus _currentIp;
	
	private void clearState() {
		_cpDebugStep12 = null;
		_cpDebugStep3 = null;
		_informationColumns = null;
		_informationRows = null;
	}

	public void run(ImageProcessor ip) {
		if (ip == null) {
			return;
		}
		clearState();
		final int width = ip.getWidth();
		final int height = ip.getHeight();
		final Borders borders;
		final ByteProcessor bpSegmentation;
		{//step1 & step2
			_informationColumns = newShortArray(_blockTotal, width);
			_informationRows = newShortArray(_blockTotal, height);
			bpSegmentation = new ByteProcessor(width, height);
			step1(ip, bpSegmentation);
			if (TableauEnvironment._debugMode) {
				_cpDebugStep12 = new ColorProcessor(width, height);
				_cpDebugStep12.copyBits(bpSegmentation,0,0,Blitter.COPY);
			}
			borders = step2(ip, bpSegmentation);
			if (TableauEnvironment._debugMode) {
				new ImagePlus("Step 1 " + _currentIp.getTitle(), bpSegmentation).show();
				drawBorders(_cpDebugStep12, borders, HORIZONTAL_CP_COLOR, VERTICAL_CP_COLOR);
				new ImagePlus("Step 2 " + _currentIp.getTitle(), _cpDebugStep12).show();
			}
		}
		{//step3
			if (TableauEnvironment._debugMode) {
				_cpDebugStep3 = new ColorProcessor(width, height);
				_cpDebugStep3.copyBits(bpSegmentation,0,0,Blitter.COPY);
			}
			step3(borders, ip);
			if (TableauEnvironment._debugMode) {
				drawBorders(_cpDebugStep3, borders, HORIZONTAL_CP_COLOR, VERTICAL_CP_COLOR);
				new ImagePlus("Step 3 " + _currentIp.getTitle(), _cpDebugStep3).show();
			}
		}
		{//step4
			step4(ip, borders);
			//currentIp.setRoi(ip.getRoi());
		}
		
	}

	private static void drawBorders(ColorProcessor debugImgBorder, Borders borders, Color hColor, Color vColor) {
		drawLines(debugImgBorder, borders._lowerBorder, hColor);
		drawLines(debugImgBorder, borders._upperBorder, hColor);
		drawLines(debugImgBorder, borders._leftBorder, vColor);
		drawLines(debugImgBorder, borders._rightBorder, vColor);
	}
	
	private static void drawLines(ColorProcessor cp, java.util.List<Point> border, Color color) {
		if (!border.isEmpty()) {
			int lineSize = getLineSize(cp);
			Point p1 = border.get(0);
			cp.moveTo(p1.x, p1.y);
			for (Point p : border) {
				cp.setColor(color);
				cp.setLineWidth(lineSize*3);
				cp.drawDot(p.x, p.y);
				cp.setLineWidth(lineSize);
				cp.lineTo(p.x, p.y);
				cp.moveTo(p.x, p.y);
			}
		}
	}

	private static int getLineSize(ImageProcessor cp) {
		int r = (int) Math.ceil(Math.max(cp.getWidth(), cp.getHeight())*0.004);
		if (r < 1) {
			return 1;
		}
		else {
			return r;
		}
	}

	private short[][] newShortArray(int s1, int s2) {
		return new short[s1][s2];
	}

	private void step1(ImageProcessor ipO, ByteProcessor ipS) {
		
		int width = ipO.getWidth();
		int height = ipO.getHeight();
		int deltaX = (int) Math.round(_relativeDiference*width);
		int deltaY = (int) Math.round(_relativeDiference*height);
		
		s1_segmentation(ipO, ipS, 0            , 0            , width/2      , height/2    , deltaX, deltaY);			
		s1_segmentation(ipO, ipS, 0            , height/2     , width/2      , height      , deltaX,-deltaY);			
		s1_segmentation(ipO, ipS, width/2      , 0            , width        , height/2    ,-deltaX, deltaY);			
		s1_segmentation(ipO, ipS, width/2      , height/2     , width        , height      ,-deltaX,-deltaY);			
		
	}
	
	//separarFundoInfoRegiao(imO,imR, xInicio, yInicio, xFim, yFim, horizontal, vertical) 
	
	private void s1_segmentation(ImageProcessor ipO, ByteProcessor ipS, int xBegin, int yBegin, int xEnd, int yEnd, int deltaX, int deltaY) {
		final int[] pO = (int[]) ipO.getPixels();
		final byte[] pD = (byte[]) ipS.getPixels();
		final int width = ipO.getWidth();
		final int height = ipO.getHeight();
		final int blockWidth = getBlockSize(width);
		final int blockHeight = getBlockSize(height);
		final int offsetDoubleDeltaY = 2*deltaY*width;
		final int doubleDeltaX = (deltaX*2);
		for (int y = yBegin; y < yEnd; y++) {
			final int offset = y * width;
			final int offsetDiference = (y+deltaY) * width;
			final int iBlockY = y / blockHeight; 
			for (int x = xBegin; x < xEnd; x++) {
				final int i = offset + x;
				final int pixelValue = pO[i];
				
				
				final int c1Diference1;
				final int c2Diference1;
				final int c3Diference1;
				{
					final int pixelDiference = pO[offsetDiference + (x + deltaX)];
					c1Diference1 = (pixelValue & 0xFF) - (pixelDiference & 0xFF);
					c2Diference1 = ((pixelValue & 0xFF00) - (pixelDiference & 0xFF00)) >> 8;
					c3Diference1 = ((pixelValue & 0xFF0000) - (pixelDiference & 0xFF0000)) >> 16;
				}
				
				final int c1Diference2;
				final int c2Diference2;
				final int c3Diference2;
				{
					final int pixelDiference = pO[(offsetDiference + offsetDoubleDeltaY) + x + doubleDeltaX];
					c1Diference2 = (pixelValue & 0xFF) - (pixelDiference & 0xFF);
					c2Diference2 = ((pixelValue & 0xFF00) - (pixelDiference & 0xFF00)) >> 8;
					c3Diference2 = ((pixelValue & 0xFF0000) - (pixelDiference & 0xFF0000)) >> 16;
				}
				
			    if ( 
			    		
				    	 (
				    			 (c1Diference1<0?-c1Diference1:c1Diference1) <= _minDiference &&
				    			 (c2Diference1<0?-c2Diference1:c2Diference1) <= _minDiference &&
				    			 (c3Diference1<0?-c3Diference1:c3Diference1) <= _minDiference
		    		     ) 
		    		     || 
		    		     ( 
		    		    		 (c1Diference2<0?-c1Diference2:c1Diference2) <= _minDiference &&
		    		    		 (c2Diference2<0?-c2Diference2:c2Diference2) <= _minDiference &&
		    		    		 (c3Diference2<0?-c3Diference2:c3Diference2) <= _minDiference
	   	    		     ) 
	   	    		     
	   	    		     
		    		     )
			    {
			    	pD[i] = PIXEL_BACKGROUND;
			    }
			    else {
			    	pD[i] = PIXEL_INFO;
					int iBlockX = x / blockWidth; 
			    	_informationColumns[iBlockY][x] = (short) (_informationColumns[iBlockY][x] + 1);
			    	_informationRows[iBlockX][y] = (short) (_informationRows[iBlockX][y] + 1);
			    }
			}
		}
	}

	private int getBlockSize(int total) {
		return (int) Math.ceil((double) total / (double) _blockTotal);
	}

	private Borders step2(ImageProcessor ipO, ByteProcessor bpSegmentation) {
		int width = ipO.getWidth();
		int height = ipO.getHeight();
		final int internal_width = (int) Math.round(width*_internalRatio);
		final int external_width = (int) Math.round(width*_externalRatio);
		final int internal_height = (int) Math.round(height*_internalRatio);
		final int external_height = (int) Math.round(height*_externalRatio);
		
		//upper border
		final java.util.List<Point> upper = s2_searchLineBorder(bpSegmentation, (height/2)-1, -1, -external_height, internal_height);
		
		//lower border
		final java.util.List<Point> lower = s2_searchLineBorder(bpSegmentation, (height/2)+1, +1, -internal_height, external_height);
		
		//left border
		final java.util.List<Point> left = s2_searchColumnBorder(bpSegmentation, (width/2)-1, -1, -external_width, internal_width);
		
		//right border
		final java.util.List<Point> right = s2_searchColumnBorder(bpSegmentation, (width/2)+1, +1, -internal_width, external_width);
		
		return new Borders(left, upper, right, lower);
	}
	
	private java.util.List<Point> s2_searchLineBorder(ByteProcessor bpSegmentation, int begin, int increment, int yBlockBegin, int yBlockEnd) {
		final int width = bpSegmentation.getWidth();
		final int height = bpSegmentation.getHeight();
		final byte[] pS = (byte[]) bpSegmentation.getPixels();
		final int blockSize = getBlockSize(width);
		final int minInfoCount = _searchPoints+1;
		final Vector<Point> points = new Vector<Point>(_blockTotal);
		for (int iControlPoint = 0; iControlPoint < _blockTotal; iControlPoint++) {
			final int searchX = (int) ((iControlPoint+0.5)*blockSize);
			int y = begin;
			WHILE: while ( (0 <= y) && (y < height) ) {
				int infoCount = 0;
				for (int i = -_searchPoints; i <= _searchPoints; i++) {
					if (pS[(y*width)+(searchX+i)] == PIXEL_INFO) {
						infoCount++;
					}
				}
				if (infoCount >= minInfoCount) {
					if (getInformationRatio(_informationRows, iControlPoint, blockSize, yBlockBegin+y, yBlockEnd+y) >= _borderInformationRatio) {
						points.add(new Point(searchX, y));
						if (_cpDebugStep12 != null) {
							_cpDebugStep12.setColor(HORIZONTAL_CP_BOX_COLOR);
							_cpDebugStep12.setLineWidth(getLineSize(bpSegmentation));
							_cpDebugStep12.drawRect(iControlPoint*blockSize, yBlockBegin+y, blockSize-1, yBlockEnd-yBlockBegin);
						}
						break WHILE;						
					}
				}
				y += increment; 
			}
		}
		return points;
	}

	private java.util.List<Point> s2_searchColumnBorder(ByteProcessor bpSegmentation, int begin, int increment, int xBlockBegin, int xBlockEnd) {
		final int width = bpSegmentation.getWidth();
		final int height = bpSegmentation.getHeight();
		final byte[] pS = (byte[]) bpSegmentation.getPixels();
		final int blockSize = getBlockSize(height);
		final int minInfoCount = _searchPoints+1;
		final Vector<Point> points = new Vector<Point>(_blockTotal);
		for (int iControlPoint = 0; iControlPoint < _blockTotal; iControlPoint++) {
			final int searchY = (int) ((iControlPoint+0.5)*blockSize);
			int x = begin;
			WHILE: while ( (0 <= x) && (x < width) ) {
				int infoCount = 0;
				for (int i = -_searchPoints; i <= _searchPoints; i++) {
					if (pS[((searchY+i)*width)+x] == PIXEL_INFO) {
						infoCount++;
					}
				}
				if (infoCount >= minInfoCount) {
					if (getInformationRatio(_informationColumns, iControlPoint, blockSize, xBlockBegin+x, xBlockEnd+x) >= _borderInformationRatio) {
						points.add(new Point(x, searchY));
						if (_cpDebugStep12 != null) {
							_cpDebugStep12.setColor(VERTICAL_CP_BOX_COLOR);
							_cpDebugStep12.setLineWidth(getLineSize(bpSegmentation));
							_cpDebugStep12.drawRect(xBlockBegin+x, iControlPoint*blockSize, xBlockEnd-xBlockBegin, blockSize-1);
						}
						break WHILE;						
					}
				}
				x += increment; 
			}
		}
		return points;
	}

	
	private double getInformationRatio(short[][] blocksSums, int controlPoint, int blockSize, int blockBegin, int blockEnd) {
		short[] controlPointSums = blocksSums[controlPoint];
		blockBegin = blockBegin<0?0:blockBegin;
		blockEnd = blockEnd>=controlPointSums.length?controlPointSums.length-1:blockEnd;
		int totalInfo = 0;
		for (int i = blockBegin; i <= blockEnd; i++) {
			totalInfo += controlPointSums[i];
		}
		int total = blockSize*(1+blockEnd-blockBegin);
		return ((double) totalInfo) / ((double) total);
	}

	private static class PointComparator implements Comparator<Point> {

		final boolean _verticalBorder;
		
		PointComparator(boolean verticalBorder) {
			_verticalBorder = verticalBorder;
		}
		
		public int compare(Point p1, Point p2) {
			final int ret;
			if (_verticalBorder) {
				if (p1.x < p2.x) {
					ret = -1;
				}
				else if (p1.x > p2.x) {
					ret = 1;
				}
				else {
					ret = 0;
				}
			}
			else {
				if (p1.y < p2.y) {
					ret = -1;
				}
				else if (p1.y > p2.y) {
					ret = 1;
				}
				else {
					ret = 0;
				}
			}
			return ret;
		}
		
	}
	
	
	/*
	private void step3(Borders borders, ImageProcessor ip) {
		s3_removeCPs(borders._upperBorder, new PointComparator(false, false, ip.getWidth()));
		s3_removeCPs(borders._lowerBorder, new PointComparator(true, false, ip.getWidth()));
		s3_removeCPs(borders._rightBorder, new PointComparator(false, true, ip.getHeight()));
		s3_removeCPs(borders._leftBorder, new PointComparator(true, true, ip.getHeight()));
		
		
		
	}
	
	private void s3_removeCPs(List<Point> hPoints, PointComparator pComparator) {
		final double minVariance =  Math.pow(pComparator.getAxisSize()*0.010, 2);
		final double minVariation = pComparator.getAxisSize()*0.020;

		if (hPoints.size() > 3) {
		
			Vector<Point> orderedPoints = new Vector<Point>(hPoints);
			Vector<Point> choosenPoints = new Vector<Point>();

			Collections.sort(orderedPoints, pComparator);
			final int middleIndex = orderedPoints.size()/2;
			final double meanMiddle;
			{
				
				int middleValue = pComparator.getValue(orderedPoints.get(middleIndex));
				int middleRight = pComparator.getValue(orderedPoints.get(middleIndex-1));
				int middleLeft = pComparator.getValue(orderedPoints.get(middleIndex+1));
				
				int totalMiddle = middleValue + middleRight + middleLeft;
				int totalMiddleSquare = (middleValue*middleValue) + (middleRight*middleRight) + (middleLeft*middleLeft);
				
				meanMiddle = totalMiddle/3.0;
				double secondMoment = totalMiddleSquare/3.0;
				
				double variance = secondMoment - (meanMiddle*meanMiddle);
				if (variance > minVariance) {
					hPoints.clear();
					return;
				}
				
				choosenPoints.add(orderedPoints.get(middleIndex));
				choosenPoints.add(orderedPoints.get(middleIndex-1));
				choosenPoints.add(orderedPoints.get(middleIndex+1));
			}
			
			double lowerValue = meanMiddle - minVariation;
			double higherValue = meanMiddle + minVariation;
			{
				for (int i = middleIndex-2; i >= 0; i--) {
					int value = pComparator.getValue(orderedPoints.get(i));
					if ( (value < lowerValue) || (higherValue < value) ){
						break;
					}
					choosenPoints.add(orderedPoints.get(i));
				}
			}
			
			{
				for (int i = middleIndex+2; i <= orderedPoints.size()-1; i++) {
					int value = pComparator.getValue(orderedPoints.get(i));
					if ( (value < lowerValue) || (higherValue < value) ){
						break;
					}
					choosenPoints.add(orderedPoints.get(i));
				}
			}
			
			
			Iterator<Point> it = hPoints.iterator();
			
			while (it.hasNext()) {
				Point p = it.next();
				if (!choosenPoints.contains(p)) {
					it.remove();
				}
			}
			
		}
		else {
			hPoints.clear();
		}
	}
	*/
	
	private void step3(Borders borders, ImageProcessor ip) {
		
		int width = ip.getWidth();
		int height = ip.getHeight();
		
		s3_removeCPs(borders._upperBorder);
		s3_removeCPs(borders._lowerBorder);
		s3_removeCPs(borders._rightBorder);
		s3_removeCPs(borders._leftBorder);
		
		{
			int minBorderY = (borders._upperBorder.size()==0)?0:Collections.min(borders._upperBorder, new PointComparator(false)).y;
			int maxBorderY = (borders._lowerBorder.size()==0)?height:Collections.max(borders._lowerBorder, new PointComparator(false)).y;
			s3_removeCornersCPsVertical(borders._leftBorder, minBorderY, maxBorderY);
			s3_removeCornersCPsVertical(borders._rightBorder, minBorderY, maxBorderY);
		}
		
		{
			int minBorderX = (borders._leftBorder.size()==0)?0:Collections.min(borders._leftBorder, new PointComparator(true)).x;
			int maxBorderX = (borders._rightBorder.size()==0)?width:Collections.max(borders._rightBorder, new PointComparator(true)).x;
			s3_removeCornersCPsHorizontal(borders._upperBorder, minBorderX, maxBorderX);
			s3_removeCornersCPsHorizontal(borders._lowerBorder, minBorderX, maxBorderX);
		}
		
		
	}

	private void s3_removeCornersCPsVertical(List<Point> vPoints, int minY, int maxY) {
		for (int i = vPoints.size() - 1; i >= 0; i--) {
			Point p = vPoints.get(i);
			if ( (p.y < minY) || (maxY < p.y) ) {
				vPoints.remove(i);
			}
		}
	}
	
	private void s3_removeCornersCPsHorizontal(List<Point> hPoints, int minX, int maxX) {
		for (int i = hPoints.size() - 1; i >= 0; i--) {
			Point p = hPoints.get(i);
			if ( (p.x < minX) || (maxX < p.x) ) {
				hPoints.remove(i);
			}
		}
	}
	
	private void s3_removeCPs(List<Point> hPoints) {
		if (hPoints.size() > 0) {
			final int minimum_low_slopes = CP_NEIGHBOORS_COMPARISION;
			final boolean[] doNotDelete = new boolean[hPoints.size()];
			for (int i = hPoints.size()-1; i >= 0; i--) {
				final int lower_index;
				if (i-CP_NEIGHBOORS_COMPARISION < 0) {
					lower_index = 0;
				}
				else {
					lower_index = i-CP_NEIGHBOORS_COMPARISION;
				}
				final int higher_index;
				if (i+CP_NEIGHBOORS_COMPARISION >= hPoints.size()) {
					higher_index = hPoints.size()-1;
				}
				else {
					higher_index = i+CP_NEIGHBOORS_COMPARISION;
				}
				int low_slope = 0;
				Point pI = hPoints.get(i);
				for (int k = lower_index; k <= higher_index; k++) {
					if (k == i) {
						continue;
					}
					Point pK = hPoints.get(k);
					int deltaX = pK.x - pI.x;
					if (deltaX < 0) {
						deltaX = -deltaX;
					}
					int deltaY = pK.y - pI.y;
					if (deltaY < 0) {
						deltaY = -deltaY;
					}
					final double slope = Math.min(deltaX, deltaY)/((double)Math.max(deltaX, deltaY));
					if (slope <= CP_MINIMUM_SLOPE) {
						low_slope++;
					}
				}
				if (low_slope >= minimum_low_slopes) {
					doNotDelete[i] = true;
				}
			}
			for (int i = hPoints.size()-1; i >= 0; i--) {
				if (!doNotDelete[i]) {
					hPoints.remove(i);
				}
			}
		}
	}

	
	private void step4(ImageProcessor ip, Borders borders) {
		final Point upperBorderLeft1, upperBorderLeft2, upperBorderRight1, upperBorderRight2;
		{//upper border
			if (borders._upperBorder.size() <= 1) {
				upperBorderLeft1 = new Point(ip.getWidth()/2,0);
				upperBorderLeft2 = new Point((ip.getWidth()/2)-1,0);
				upperBorderRight1 = upperBorderLeft1;
				upperBorderRight2 = upperBorderLeft2;
			}
			else {
				upperBorderLeft1 = borders._upperBorder.get(0);
				upperBorderLeft2 = borders._upperBorder.get(1);
				upperBorderRight1 = borders._upperBorder.get(borders._upperBorder.size()-1);
				upperBorderRight2 = borders._upperBorder.get(borders._upperBorder.size()-2);
			}
		}
		final Point lowerBorderLeft1, lowerBorderLeft2, lowerBorderRight1, lowerBorderRight2;
		{//lower border
			if (borders._lowerBorder.size() <= 1) {
				lowerBorderLeft1 = new Point(ip.getWidth()/2,ip.getHeight()-1);
				lowerBorderLeft2 = new Point((ip.getWidth()/2)-1,ip.getHeight()-1);
				lowerBorderRight1 = lowerBorderLeft1;
				lowerBorderRight2 = lowerBorderLeft2;
			}
			else {
				lowerBorderLeft1 = borders._lowerBorder.get(0);
				lowerBorderLeft2 = borders._lowerBorder.get(1);
				lowerBorderRight1 = borders._lowerBorder.get(borders._lowerBorder.size()-1);
				lowerBorderRight2 = borders._lowerBorder.get(borders._lowerBorder.size()-2);
			}
		}
		final Point leftBorderUp1, leftBorderUp2, leftBorderDown1, leftBorderDown2;
		{//left border
			if (borders._leftBorder.size() <= 1) {
				leftBorderUp1 = new Point(0,(ip.getHeight()/2));
				leftBorderUp2 = new Point(0,(ip.getHeight()/2)-1);
				leftBorderDown1 = leftBorderUp1;
				leftBorderDown2 = leftBorderUp2;
			}
			else {
				leftBorderUp1 = borders._leftBorder.get(0);
				leftBorderUp2 = borders._leftBorder.get(1);
				leftBorderDown1 = borders._leftBorder.get(borders._leftBorder.size()-1);
				leftBorderDown2 = borders._leftBorder.get(borders._leftBorder.size()-2);
			}
		}
		final Point rightBorderUp1, rightBorderUp2, rightBorderDown1, rightBorderDown2;
		{//right border
			if (borders._rightBorder.size() <= 1) {
				rightBorderUp1 = new Point(ip.getWidth()-1,(ip.getHeight()/2));
				rightBorderUp2 = new Point(ip.getWidth()-1,(ip.getHeight()/2)-1);
				rightBorderDown1 = rightBorderUp1;
				rightBorderDown2 = rightBorderUp2;
			}
			else {
				rightBorderUp1 = borders._rightBorder.get(0);
				rightBorderUp2 = borders._rightBorder.get(1);
				rightBorderDown1 = borders._rightBorder.get(borders._rightBorder.size()-1);
				rightBorderDown2 = borders._rightBorder.get(borders._rightBorder.size()-2);
			}
		}
		Point cornerUpperLeft = s4_findJoinPoint(upperBorderLeft1, upperBorderLeft2, leftBorderUp1, leftBorderUp2);
		Point cornerUpperRight = s4_findJoinPoint(upperBorderRight1, upperBorderRight2, rightBorderUp1, rightBorderUp2);
		Point cornerLowerRight = s4_findJoinPoint(lowerBorderRight1, lowerBorderRight2, rightBorderDown1, rightBorderDown2);
		Point cornerLowerLeft = s4_findJoinPoint(lowerBorderLeft1, lowerBorderLeft2, leftBorderDown1, leftBorderDown2);
		{//setting roi
			Polygon perspectivePolygon = new Polygon();
			perspectivePolygon.addPoint(cornerUpperLeft.x, cornerUpperLeft.y);
			perspectivePolygon.addPoint(cornerUpperRight.x, cornerUpperRight.y);
			perspectivePolygon.addPoint(cornerLowerRight.x, cornerLowerRight.y);
			perspectivePolygon.addPoint(cornerLowerLeft.x, cornerLowerLeft.y);
			_currentIp.setRoi(new PolygonRoi(perspectivePolygon, PolygonRoi.POLYGON));
		}
	}
	
	private Point s4_findJoinPoint(Point p1_1, Point p1_2, Point p2_1, Point p2_2) {
		final double joinX, joinY;
		AandB line1 = new AandB(p1_1, p1_2);
		AandB line2 = new AandB(p2_1, p2_2);
		if (!Double.isNaN(line1.a)) {
			if (!Double.isNaN(line2.a)) {
				final double deltaA = line1.a - line2.a;
				final double deltaB = line1.b - line2.b;
				joinX = -(deltaB/deltaA);
				joinY = (line1.a*joinX) + line1.b;
			}
			else {
				joinX = p2_1.x;
				joinY = (line1.a*joinX) + line1.b;
			}
		}
		else {
			joinX = p1_1.x;
			joinY = (line2.a*joinX) + line2.b;
		}
		return new Point((int) Math.round(joinX), (int) Math.round(joinY));
	}
	
	private static class AandB {
		final double a, b;
		private AandB(Point p1, Point p2) {
			{
				int deltaX = p1.x - p2.x;
				int deltaY = p1.y - p2.y;
				if (deltaX == 0) {
					a = Double.NaN;
					b = Double.NaN;
				}
				else {
					a = deltaY/((double)deltaX);
					b = p1.y - (a*p1.x);
				}
				
			}

		}
	}
	
	/*
function [xInterseccao, yInterseccao] = encontrarInterseccao(pontos1, pontos2)
  
    [a1, b1] = calcularAeB(pontos1);
    [a2, b2] = calcularAeB(pontos2);
    
    if (a1 < Inf)
      if (a2 < Inf)
        deltaA = a1 - a2;
        deltaB = b1 - b2;
        xInterseccao = -(deltaB/deltaA);
        yInterseccao = (a1*xInterseccao) + b1;
      else
        xInterseccao = pontos2(1,1);
        yInterseccao = (a1*xInterseccao) + b1;
      end
    else
      xInterseccao = pontos1(1,1);
      yInterseccao = (a2*xInterseccao) + b2;
    end
    
    xInterseccao = round(xInterseccao);
    yInterseccao = round(yInterseccao);
    

function [a, b] = calcularAeB(pontos)
    deltaX = pontos(1,1) - pontos(2,1);
    deltaY = pontos(1,2) - pontos(2,2);
    if deltaX == 0
      a = Inf;
      b = Inf;
    else
      a = deltaY/deltaX;
      b = pontos(1,2) - (a*pontos(1,1));
    end
    */

	public int setup(String arg, ImagePlus ip) {
		if (arg.equals("about")) {
			showAbout();
			return DONE;
		}
		_currentIp = ip;
		return DOES_RGB + NO_CHANGES;
	}

	private void showAbout() {
		IJ
				.showMessage(
						"About " + this.getClass().getName() + "...",
						"This plugin is a implementation of the Tableu Border Remover algorithm "
								+ " to detect border of boards (blackboards, greenboards and whiteboards) developted by"
								+ " Rafael Dueire Lins and Daniel Marques Oliveira");
	}
	
	private static class Borders {
		public Borders(java.util.List<Point> left, java.util.List<Point> upper, java.util.List<Point> right, java.util.List<Point> lower) {
			_leftBorder = left;
			_lowerBorder = lower;
			_rightBorder = right;
			_upperBorder = upper;
		}
		java.util.List<Point> _upperBorder;
		java.util.List<Point> _lowerBorder;
		java.util.List<Point> _leftBorder;
		java.util.List<Point> _rightBorder;
	}


}
